﻿using System;
using System.Collections.Generic;
using Microsoft.Goldenlight.Collections;
using Microsoft.Goldenlight.Resources;

namespace Microsoft.Goldenlight.Framework
{
    /// <summary>
    /// 	Utility class for creating object pools for objects of any type. An object pool is a collection of objects which have already been used once and are ready for recycled use.
    /// </summary>
    /// <remarks>
    /// 	Using an object pooler can reduce memory churn in some cases. Overuse of object poolers can cause more memory fragmentation than otherwise expected.
    /// </remarks>
    public static class ObjectPooler
    {
        #region Fields & Properties

        private static readonly Dictionary<Type, DoublyLinkedList<LifetimeObject>> s_TypedPool = new Dictionary<Type, DoublyLinkedList<LifetimeObject>>();

        #endregion

        #region Create

        /// <summary>
        /// 	Recover object of given type from object pool or create it if pool is empty.
        /// </summary>
        /// <param name="objectType"> The specified type of object to create. </param>
        /// <returns> Type of object to find or construct. </returns>
        public static LifetimeObject Create(Type objectType)
        {
            if (objectType == null)
            {
                throw new ArgumentNullException("objectType");
            }
            if (!objectType.IsSubclassOf(typeof (LifetimeObject)))
            {
                throw new ArgumentException(Resx.TypeIsNotDerivedFromLifetimeObject, "objectType");
            }
            return Take(objectType) ?? Activator.CreateInstance(objectType) as LifetimeObject;
        }

        /// <summary>
        /// 	Recover object of given type from object pool or create it if pool is empty.
        /// </summary>
        /// <returns> Type of object to find or construct. </returns>
        public static T Create<T>() where T : LifetimeObject, new()
        {
            return Take<T>() ?? new T();
        }

        #endregion

        #region Take

        /// <summary>
        /// 	Take object of specified type in object pool.
        /// </summary>
        /// <param name="objectType"> The specified type of object to take. </param>
        /// <returns> Null if none found. Use Create if you want an object returned no matter what. </returns>
        public static LifetimeObject Take(Type objectType)
        {
            if (objectType == null)
            {
                throw new ArgumentNullException("objectType");
            }
            DoublyLinkedList<LifetimeObject> objectPool;
            if (s_TypedPool.TryGetValue(objectType, out objectPool) && objectPool != null)
            {
                var node = objectPool.First;
                if (node != null)
                {
                    var value = node.Value;
                    objectPool.Remove(node);
                    value.IsDisposed = false;
                    return value;
                }
            }
            objectPool = new DoublyLinkedList<LifetimeObject>();
            s_TypedPool[objectType] = objectPool;
            return null;
        }

        /// <summary>
        /// 	Take object of specified type in object pool.
        /// </summary>
        /// <returns> Null if none found. Use Create if you want an object returned no matter what. </returns>
        public static T Take<T>() where T : LifetimeObject
        {
            var objectType = typeof (T);
            DoublyLinkedList<LifetimeObject> objectPool;
            if (s_TypedPool.TryGetValue(objectType, out objectPool) && objectPool != null)
            {
                var node = objectPool.First;
                if (node != null)
                {
                    var value = node.Value;
                    objectPool.Remove(node);
                    value.IsDisposed = false;
                    return value as T;
                }
            }
            objectPool = new DoublyLinkedList<LifetimeObject>();
            s_TypedPool[objectType] = objectPool;
            return default(T);
        }

        #endregion

        #region Return

        /// <summary>
        /// 	Place an object into the object pool.
        /// </summary>
        /// <param name="objectType"> The specified type of object to return. </param>
        /// <param name="value"> Object to recycle. </param>
        /// <returns> True if recycle successfully. </returns>
        public static bool Return(Type objectType, LifetimeObject value)
        {
            if (objectType == null)
            {
                throw new ArgumentNullException("objectType");
            }
            DoublyLinkedList<LifetimeObject> objectPool;
            if (!s_TypedPool.TryGetValue(objectType, out objectPool) || objectPool == null)
            {
                objectPool = new DoublyLinkedList<LifetimeObject>();
                s_TypedPool[objectType] = objectPool;
            }
            if (value != null)
            {
                objectPool.AddLast(value);
                return true;
            }
            return false;
        }

        /// <summary>
        /// 	Place an object into the object pool.
        /// </summary>
        /// <param name="value"> Object to recycle. </param>
        /// <returns> True if recycle successfully. </returns>
        public static bool Return<T>(T value) where T : LifetimeObject
        {
            var objectType = typeof (T);
            DoublyLinkedList<LifetimeObject> objectPool;
            if (!s_TypedPool.TryGetValue(objectType, out objectPool) || objectPool == null)
            {
                objectPool = new DoublyLinkedList<LifetimeObject>();
                s_TypedPool[objectType] = objectPool;
            }
            if (value != null)
            {
                objectPool.AddLast(value);
                return true;
            }
            return false;
        }

        #endregion

        #region Count

        /// <summary>
        /// 	Gets how many recycled objects in the pool.
        /// </summary>
        public static int GetCount<T>()
        {
            var objectType = typeof (T);
            DoublyLinkedList<LifetimeObject> objectPool;
            if (!s_TypedPool.TryGetValue(objectType, out objectPool) || objectPool == null)
            {
                objectPool = new DoublyLinkedList<LifetimeObject>();
                s_TypedPool[objectType] = objectPool;
                return 0;
            }
            return objectPool.Count;
        }

        #endregion
    }
}